package com.wqiuqiu.gulimall.secdkill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.wqiuqiu.common.R;
import com.wqiuqiu.common.to.mq.SecdKillOrderTo;
import com.wqiuqiu.gulimall.secdkill.feign.CouponFeignService;
import com.wqiuqiu.gulimall.secdkill.feign.ProductFeignService;
import com.wqiuqiu.gulimall.secdkill.interceptor.SecdkillInterceptor;
import com.wqiuqiu.gulimall.secdkill.service.SecdKillService;
import com.wqiuqiu.gulimall.secdkill.to.SecdKillRedisTo;
import com.wqiuqiu.gulimall.secdkill.vo.SecdKillSessionVo;
import com.wqiuqiu.gulimall.secdkill.vo.SecdKillSessionWithSkusVo;
import com.wqiuqiu.gulimall.secdkill.vo.SecdKillSkusVo;
import com.wqiuqiu.gulimall.secdkill.vo.SkuInfoVo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SecdKillServiceImpl implements SecdKillService {

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redisson;
    @Autowired
    RabbitTemplate rabbitTemplate;

    private static final String SESSIONS_CACHE_PREFIX = "seckill:session:";
    private static final String SKUKILL_CACHE_PREFIX = "seckill:skus:";
    private static final String SKU_STOCK_CACHE_PREFIX = "seckill:stock:";
    private static final String USER_KILL_CACHE_PREFIX = "seckill:user:";

    @Override
    public void uploadSeckillSku() {

        R r = couponFeignService.getLates3DaySession();
        if (r.getCode() == 0) {
            List<SecdKillSessionWithSkusVo> vos = r.getData(new TypeReference<List<SecdKillSessionWithSkusVo>>() {
            });
            if (vos != null) {
                //存入redis
                for (SecdKillSessionWithSkusVo data : vos) {
                    Boolean isCurrent = stringRedisTemplate.hasKey(SKUKILL_CACHE_PREFIX + data.getSessions().getId());
                    if (isCurrent == null || !isCurrent) {
                        //1 存入秒杀会话信息
                        saveSessionInfo(data.getSessions());
                        //2 存入秒杀会话关联的商品信息
                        saveSkuInfo(data.getSessions(), data.getSkus());
                    }
                }


            }
        }
    }

    @Override
    public List<SecdKillRedisTo> getCurrentSeckillSessions() {
        List<SecdKillRedisTo> tos = new ArrayList<>();
        long now = new Date().getTime();
        String pattern = SESSIONS_CACHE_PREFIX + "*";

        Set<String> keys = stringRedisTemplate.keys(pattern);
        for (String key : keys) {
            String s = stringRedisTemplate.opsForValue().get(key);
            String[] split = s.split(":");
            if (Long.parseLong(split[0]) <= now && Long.parseLong(split[1]) >= now) {
                List<Object> values = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX + key.split(SESSIONS_CACHE_PREFIX)[1]).values();
                for (Object value : values) {
                    SecdKillRedisTo secdKillRedisTo = JSON.parseObject(value.toString(), SecdKillRedisTo.class);
                    tos.add(secdKillRedisTo);
                }
            }
        }
        return tos;
    }

    @Override
    public SecdKillRedisTo getSecdKillInfoBySkuId(Long skuId) {
        String pattern = SKUKILL_CACHE_PREFIX + "*";
        Set<String> keys = stringRedisTemplate.keys(pattern);
        for (String key : keys) {
            Boolean has = stringRedisTemplate.opsForHash().hasKey(key, String.valueOf(skuId));
            if (has) {
                String data = (String) stringRedisTemplate.opsForHash().get(key, String.valueOf(skuId));
                return JSON.parseObject(data, SecdKillRedisTo.class);
            }
        }
        return null;
    }

    @Override
    public SecdKillOrderTo kill(String code, String killId, int num) {
        //数据校验
        if (StringUtils.isEmpty(code) || StringUtils.isEmpty(killId) || num < 0)
            return null;
        String[] split = killId.split("-");
        String userId = split[0];
        String session = split[1];
        String skuId = split[2];

        String stockKey = SKU_STOCK_CACHE_PREFIX + session + "-" + skuId;

        Boolean hasKey = stringRedisTemplate.hasKey(stockKey);
        if (hasKey) {
            //验证code
            BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX + session);
            String json = ops.get(skuId);
            SecdKillRedisTo redisTo = JSON.parseObject(json, SecdKillRedisTo.class);
            if (!code.equals(redisTo.getRandCode()))
                return null;

            //存在秒杀活动，判断该用户是否参与过秒杀，或者是否达到限购数量
            Boolean isKilled = stringRedisTemplate.hasKey(USER_KILL_CACHE_PREFIX + killId);
            if (!isKilled) {
                long endTime = Long.parseLong(stringRedisTemplate.opsForValue().get(SESSIONS_CACHE_PREFIX + session).split(":")[1]);
                long timeout = endTime - new Date().getTime();
                //设置过期时间
                stringRedisTemplate.opsForValue().set(USER_KILL_CACHE_PREFIX + killId, String.valueOf(num), timeout, TimeUnit.MILLISECONDS);

            } else {
                int killedNum = Integer.parseInt(stringRedisTemplate.opsForValue().get(USER_KILL_CACHE_PREFIX + killId));
                int limitKillNum = redisTo.getSecdKillSkusVo().getSeckillLimit().intValue();
                if (killedNum >= limitKillNum) {
                    return null;
                }
            }
            //占坑
            RSemaphore semaphore = redisson.getSemaphore(stockKey);
            try {
                boolean acquire = semaphore.tryAcquire(num, 10L, TimeUnit.MILLISECONDS);
                if (acquire) {
                    //给MQ发送订单创建消息
                    String orderSn = IdWorker.getTimeId().substring(0, 31);
                    SecdKillOrderTo orderTo = new SecdKillOrderTo();
                    orderTo.setOrderSn(orderSn);
                    orderTo.setMemberId(SecdkillInterceptor.threadLocal.get().getId());
                    orderTo.setNum(num);
                    orderTo.setPromotionSessionId(Long.parseLong(session));
                    orderTo.setSeckillPrice(redisTo.getSecdKillSkusVo().getSeckillPrice());
                    orderTo.setSkuId(redisTo.getSkuInfo().getSkuId());
                    //给MQ发送消息
                    rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                    return orderTo;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                semaphore.release();
            }

        }


        return null;
    }

    private void saveSkuInfo(SecdKillSessionVo sessionVo, List<SecdKillSkusVo> skus) {

        String key = SKUKILL_CACHE_PREFIX + skus.get(0).getPromotionSessionId();
        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(key);

        Map<Long, SecdKillSkusVo> skusVoMap = skus.stream().collect(Collectors.toMap(SecdKillSkusVo::getSkuId, SecdKillSkusVo -> SecdKillSkusVo));
        R r = productFeignService.batchInfobyIds(skus.stream().map(SecdKillSkusVo::getSkuId).collect(Collectors.toList()));
        if (r.getCode() == 0) {
            List<SkuInfoVo> data = r.getData(new TypeReference<List<SkuInfoVo>>() {
            });
            for (SkuInfoVo skuInfoVo : data) {
                SecdKillRedisTo redisTo = new SecdKillRedisTo();
                //存入Sku的基本信息
                redisTo.setSkuInfo(skuInfoVo);
                //存入Sku的秒杀信息
                SecdKillSkusVo killSkusVo = skusVoMap.get(skuInfoVo.getSkuId());

                redisTo.setSecdKillSkusVo(killSkusVo);
                //随机码
                String uuid = UUID.randomUUID().toString().replace("-", "");
                redisTo.setRandCode(uuid);

                //时间
                redisTo.setStartTime(sessionVo.getStartTime().getTime());
                redisTo.setEndTime(sessionVo.getEndTime().getTime());
                //信号量
                RSemaphore semaphore = redisson.getSemaphore(SKU_STOCK_CACHE_PREFIX + killSkusVo.getPromotionSessionId() + "-" + skuInfoVo.getSkuId().toString());
                semaphore.trySetPermits(killSkusVo.getSeckillCount().intValue());

                ops.put(skuInfoVo.getSkuId().toString(), JSON.toJSONString(redisTo));
            }

        }


    }

    private void saveSessionInfo(SecdKillSessionVo sessions) {
        String key = SESSIONS_CACHE_PREFIX + sessions.getId().toString();
        stringRedisTemplate.opsForValue().set(key, sessions.getStartTime().getTime() + ":" +
                sessions.getEndTime().getTime());
    }
}
